using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Diagnostics;

namespace DaViL.Controls
{
    public class TemporalChart : Control
    {
        public TemporalChart()
        {
            InitializeComponent();

            _lines = new List<ChartLine>();
            _gridPen = new Pen(Color.FromArgb(204, 221, 238));
            _gridPen.DashStyle = DashStyle.Dot;
            _headerFont = this.Font;
            _title = "Bruno Fortunato";
            _titleFont = new Font(Font.FontFamily, 10.0f, FontStyle.Bold);
            _legendFont = new Font(Font.FontFamily, 8.0f, FontStyle.Regular);
            _legendBrush = new SolidBrush(Color.FromArgb(244, 247, 251));
        }

        private readonly float _margin = 25;
        private readonly float _pointSize = 4.0f;
        private readonly float _dateLineSize = 4.0f;
        private readonly int _legendWidth = 100;
        private readonly int _legendColorWidth = 10;
        private readonly int[] _valueStepPossibilities = { 1, 2, 5 };        
        private int _valueStep = 20;
        private readonly int _preferredValueStep = 20;
        
        private DateTime _minDate;
        private DateTime _maxDate;
        private DateTime _minDrawDate;
        private DateTime _maxDrawDate;

        private int _minValue;
        private int _maxValue;
        private int _minDrawValue;
        private int _maxDrawValue;

        private Pen _gridPen;
        private Font _headerFont;
        private Font _titleFont;
        private Font _legendFont;
        private Brush _legendBrush;

        private string _title;

        public string Title
        {
            get { return _title; }
            set { _title = value; }
        }
	

        protected override void OnPaint(PaintEventArgs e)
        {
            if (CanPaint())
            {
                Update(e.Graphics);
                Draw(e.Graphics);
            }
            else
            {
                e.Graphics.Clear(Color.Violet);
            }
        }

        private bool CanPaint()
        {
            bool canPaint = true;

            if (_lines.Count == 0)
                canPaint = false;
            else
            {
                bool allEmpty = true;

                foreach (ChartLine line in _lines)
                {
                    if (line.Points.Count > 0)
                    {
                        allEmpty = false;
                        break;
                    }
                }

                if (allEmpty)
                    canPaint = false;
            }

            return canPaint;
        }

        private List<ChartLine> _lines;

        public List<ChartLine> Lines
        {
            get { return _lines; }
            set { _lines = value; }
        }

        private void Update(Graphics g)
        {
            ComputeDatesAndValuesMinMax();
        }

        private void ComputeDatesAndValuesMinMax()
        {
            bool firstSet = false;

            foreach (ChartLine line in _lines)
            {
                foreach (ChartPoint point in line.Points)
                {
                    if (!firstSet)
                    {
                        _minDate = point.Date;
                        _maxDate = point.Date;
                        _minValue = point.Value;
                        _maxValue = point.Value;
                        
                        firstSet = true;
                        continue;
                    }
                    
                    _minDate = new DateTime(Math.Min(_minDate.Ticks, point.Date.Ticks));
                    _maxDate = new DateTime(Math.Max(_maxDate.Ticks, point.Date.Ticks));
                    _minValue = Math.Min(_minValue, point.Value);
                    _maxValue = Math.Max(_maxValue, point.Value);
                }
            }

            ComputeValuesStep();

            int rest = _minValue % _valueStep;
            if (rest != 0)
            {
                int toSub = _valueStep - Math.Abs(rest);
                _minDrawValue = _minValue - toSub;
            }
            else
                _minDrawValue = _minValue;

            rest = _maxValue % _valueStep;
            if (rest != 0)
            {
                int toAdd = _valueStep - Math.Abs(rest);
                _maxDrawValue = _maxValue + toAdd;
            }
            else
                _maxDrawValue = _maxValue;

            _minDrawDate = FindMonday(_minDate.Date);
            _maxDrawDate = FindSunday(_maxDate.Date).AddDays(1);

            Debug.WriteLine(String.Format("_minDrawValue: {0}", _minDrawValue));
            Debug.WriteLine(String.Format("_maxDrawValue: {0}", _maxDrawValue));
            Debug.WriteLine(String.Format("_minDate: {0}", _minDate));
            Debug.WriteLine(String.Format("_maxDate: {0}", _maxDate));
            Debug.WriteLine(String.Format("_minDrawDate: {0}", _minDrawDate));
            Debug.WriteLine(String.Format("_maxDrawDate: {0}", _maxDrawDate));
        }

        private void ComputeValuesStep()
        {
            int totalValues = _maxValue - _minValue;

            _valueStep = NormalizeValueStep(totalValues);
        }

        private int NormalizeValueStep(int totalValues)
        {
            int perfectStep = totalValues / _preferredValueStep;
            int multiplier = ComputeMultiplier(perfectStep);
            
            int last = Math.Abs((_valueStepPossibilities[0] * multiplier) - perfectStep);
            int nearestStep = _valueStepPossibilities[0] * multiplier;

            foreach (int poss in _valueStepPossibilities)
            {
                int current = Math.Abs((poss * multiplier) - perfectStep);
                if (current < last)
                {
                    nearestStep = (poss * multiplier);
                    last = current;
                }
            }

            Debug.WriteLine(String.Format("nearestStep: {0}", nearestStep));
            return nearestStep;
        }

        private int ComputeMultiplier(int perfectStep)
        {
            int multiplier = 1;
            float division = (float)perfectStep / (float)multiplier;
            while (division < 0 || division > 10)
            {
                multiplier *= 10;
                division = (float)perfectStep / (float)multiplier;
            }

            Debug.WriteLine(String.Format("multiplier: {0}", multiplier));
            return multiplier;
        }

        private DateTime FindSunday(DateTime date)
        {
            DateTime sunday = date;

            while (sunday.DayOfWeek != DayOfWeek.Sunday)
                sunday = sunday.AddDays(1);

            return sunday;
        }

        private DateTime FindMonday(DateTime date)
        {
            DateTime monday = date;

            while (monday.DayOfWeek != DayOfWeek.Monday)
                monday = monday.AddDays(-1);

            return monday;
        }

        private void Draw(Graphics g)
        {
            g.Clear(Color.White);
            g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;

            DrawGrid(g);
            DrawLine(g);
            DrawTitle(g);
            DrawLegend(g);
        }

        private void DrawLegend(Graphics g)
        {
            int space = 2;
            int height = (int)g.MeasureString("test", _legendFont).Height + space;
            int legendHeight = (height) * _lines.Count + space;
            int x = Width - _legendWidth - (int)_margin + 5;

            g.SmoothingMode = SmoothingMode.Default;
            g.FillRectangle(_legendBrush, x, _margin, _legendWidth, legendHeight);

            int index = 0;
            foreach (ChartLine line in _lines)
            {
                int x1 = x + space;
                int y1 = index * height + space + (int)_margin;
                
                Brush fill = new SolidBrush(line.Color);
                g.FillRectangle(fill, x1, y1, _legendColorWidth, height - space);
                g.DrawString(line.Description, _legendFont, fill, x1 + _legendColorWidth + space, y1);
                index++;
            }

            g.SmoothingMode = SmoothingMode.HighQuality;
        }

        private void DrawGrid(Graphics g)
        {
            DrawValuesGrid(g);
            DrawDatesGrid(g);
            DrawMainLines(g);
        }

        private void DrawTitle(Graphics g)
        {
            SizeF titleSize = g.MeasureString(_title, _titleFont);
            g.DrawString(_title, _titleFont, Brushes.DarkRed, ((Width  - _legendWidth - 10) / 2) - (titleSize.Width / 2), 2);
        }

        private void DrawMainLines(Graphics g)
        {
            g.DrawLine(Pens.Black, _margin, Height - _margin, (Width - _legendWidth - 10) - _margin, Height - _margin);
            g.DrawLine(Pens.Black, _margin, Height - _margin, _margin, _margin);
        }

        private void DrawDatesGrid(Graphics g)
        {
            int workWidth = (Width - _legendWidth - 10) - (int)_margin * 2;
            TimeSpan span = _maxDrawDate.Subtract(_minDrawDate);
            int nDates = span.Days / 7;
            if (span.Days % 7 != 0)
                throw new Exception("Check min and max draw date compution");
            nDates++;
            float columnSize = (float)workWidth / (float)nDates;

            for (int i = 0; i <= nDates; i++)
            {
                DateTime currentDate = _minDrawDate.AddDays(i * 7);
                float x = (float)i * columnSize + _margin;

                string label = currentDate.ToShortDateString();
                SizeF labelSize = g.MeasureString(label, _headerFont);
                g.DrawString(label, _headerFont, Brushes.Black, x - (labelSize.Width / 2), Height - _margin + 3);
                g.DrawLine(Pens.Black, x, Height - _margin - (_dateLineSize / 2), x, Height - _margin + (_dateLineSize / 2));
            }
        }

        private void DrawValuesGrid(Graphics g)
        {
            int workHeight = Height - (int)_margin * 2;
            int totalValues = _maxDrawValue - _minDrawValue;
            int nValues = totalValues / _valueStep;
            float rowSize = (float)workHeight / (float)nValues;

            for (int i = 0; i <= nValues; i++)
            {
                int currentValue = (i * _valueStep) - Math.Abs(_minDrawValue);
                float y = ((float)i * rowSize) + _margin;
                y = Height - y; //inversion

                Debug.WriteLine(String.Format("grid y: {0} - currentValue: {1}", y, currentValue));

                g.SmoothingMode = SmoothingMode.Default;


                string label = currentValue.ToString();
                SizeF labelSize = g.MeasureString(label, _headerFont);
                g.DrawString(label, _headerFont, Brushes.Black, _margin - (labelSize.Width), y - (labelSize.Height / 2));
                g.DrawLine(_gridPen, _margin, y, (Width - _legendWidth - 10) - _margin, y);
                g.SmoothingMode = SmoothingMode.HighQuality;
            }
        }

        private void DrawLine(Graphics g)
        {
            foreach (ChartLine line in _lines)
            {
                GraphicsPath path = new GraphicsPath();

                PointF? p1 = null;
                PointF? p2 = null;

                foreach (ChartPoint chartPoint in line.Points)
                {
                    PointF point = ComputePoint(chartPoint);
                    if (p1 == null)
                        p1 = point;
                    else
                        p2 = point;

                    DrawPoint(g, point);

                    if (p1.HasValue && p2.HasValue)
                    {
                        path.AddLine(p1.Value, p2.Value);
                        p1 = p2;
                        p2 = null;
                    }
                }

                g.DrawPath(new Pen(new SolidBrush(line.Color), 1.0f), path);
            }
        }

        private void DrawPoint(Graphics g, PointF point)
        {
            g.FillEllipse(Brushes.Red, point.X - (_pointSize / 2), point.Y - (_pointSize / 2), _pointSize, _pointSize);
        }

        private PointF ComputePoint(ChartPoint chartPoint)
        {
            int totalValues = _maxDrawValue - _minDrawValue;
            int workHeight = Height - (int)_margin * 2;
            float y = (float)workHeight * ((float)chartPoint.Value - (float)_minDrawValue) / (float)totalValues;
            y += _margin;
            y = Height - y; //inversion
            

            Debug.WriteLine(String.Format("y: {0} - value: {1}", y, chartPoint.Value));

            long totalDates = _maxDrawDate.Ticks - _minDrawDate.Ticks;
            int workWidth = (Width - _legendWidth - 10) - (int)_margin * 2;
            float x = ((float)workWidth * ((float)chartPoint.Date.Ticks - (float)_minDrawDate.Ticks) / (float)totalDates);
            x += _margin;
            
            return new PointF(x, y);
        }

        private void InitializeComponent()
        {
            this.SuspendLayout();
            // 
            // uxTemporalChart
            // 
            this.Font = new System.Drawing.Font("Tahoma", 6F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
            this.Size = new System.Drawing.Size(640, 480);
            this.ResumeLayout(false);

            SetStyle(ControlStyles.ResizeRedraw, true);
        }
    }
}
